package com.bardsoftware.foronuvolo.data;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.logging.Logger;

import javax.jdo.JDOObjectNotFoundException;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import javax.jdo.annotations.Extension;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.NotPersistent;
import javax.jdo.annotations.Order;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
import javax.jdo.listener.DetachCallback;

import org.compass.annotations.Searchable;
import org.compass.annotations.SearchableId;
import org.compass.annotations.SearchableProperty;



import com.bardsoftware.foronuvolo.server.UserService;
import com.google.appengine.repackaged.com.google.common.collect.ImmutableList;
import com.google.appengine.repackaged.com.google.common.collect.Lists;
import com.google.appengine.repackaged.com.google.common.collect.Sets;

@PersistenceCapable(identityType = IdentityType.APPLICATION, detachable = "true")
@Searchable
public class Discussion implements DetachCallback {
    public static final String MAX_REF_ID = "99991231235959";

    private static final Logger LOGGER = Logger.getLogger(Discussion.class.getName());

    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    @SearchableId
    private Long id;

    @Persistent(mappedBy = "myDiscussion", defaultFetchGroup = "true")
    private List<Message> messages = new ArrayList<Message>();

    @Persistent
    private String creationOrder;

    @NotPersistent
    private Message myInitialMessage;

    @Persistent
    private Long sourceForgeID;

    @NotPersistent
    private ArrayList<Message> mySorted;

    private static SimpleDateFormat ourCreationOrderFormat = new SimpleDateFormat("yyyyMMddHHmmss");
    private Discussion() {
    }

    public Discussion(ForumUser author, String messageContent) {
        myInitialMessage = new Message(null, author, messageContent, new Date());
        messages.add(myInitialMessage);
        updateRefID();
    }

    public Discussion(List<Message> messages) {
        this.messages.addAll(messages);
        sortMessages(this.messages);
        myInitialMessage = CollectionsUtil.first(this.messages);
        updateRefID();
    }

    void updateRefID() {
        String userRefID = getInitialMessage().getUserRefID();
        creationOrder = ourCreationOrderFormat.format(getInitialMessage().getCreation()) + "_" + userRefID;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Discussion == false) {
            return false;
        }
        return getID().equals(
                ((Discussion)obj).getID());
    }

    @Override
    public int hashCode() {
        return getID().hashCode();
    }

    public Long getID() {
        return this.id;
    }

    Message getInitialMessage() {
        if (myInitialMessage == null) {
            myInitialMessage = CollectionsUtil.first(this.messages);
        }
        return myInitialMessage;
    }

    Message getLastAnswer() {
        if (this.messages == null || this.messages.size()==1) {
            return null;
        }
        return this.messages.get(this.messages.size()-1);
    }

    public void addMessage(final Message m) {
        boolean result = PMF.runInTransaction(this, new Runnable() {
            public void run() {
                Discussion.this.messages.add(m);
                sortMessages(Discussion.this.messages);
                updateRefID();
            }
        }, 3);
        if (!result) {
            LOGGER.severe("Failed to add message=" + m.getID() + " to discussion="+id+" sfID=" + sourceForgeID);
        }
    }

    public void addAllMessagesInTransaction(final List<Message> newMessages) {
        boolean result = PMF.runInTransaction(this, new Runnable() {
            public void run() {
                addAllMessages(newMessages);
            }
        }, 3);
        if (!result) {
            LOGGER.severe("Failed to add " + newMessages.size() + " messages to discussion="+id+" sfID=" + sourceForgeID);
        }
    }

    public void addAllMessages(final List<Message> newMessages) {
        Discussion.this.messages.addAll(newMessages);
        sortMessages(this.messages);
        myInitialMessage = CollectionsUtil.first(this.messages);
        updateRefID();
    }

    private void sortMessages(List<Message> unsorted) {
        Collections.sort(unsorted, new Comparator<Message>() {
            public int compare(Message o1, Message o2) {
                if (o1 == null || o2 == null) {
                    return 0;
                }
                return o1.getCreation().compareTo(o2.getCreation());
            }
        });
    }

    public String getCreationOrder() {
        return creationOrder;
    }
    Integer getReplyCount() {
        return hasMessages() ? messages.size() - 1 : 0;
    }

    public boolean hasMessages() {
        return !messages.isEmpty();
    }

    public void save() {
        boolean result = PMF.runInTransaction(this, new Runnable() {
            public void run() {
            }
        }, 3);
        if (!result) {
            LOGGER.severe("Failed to save discussion="+id+" sfID=" + sourceForgeID);
        }
    }

    @SearchableProperty
    public String getIndexableContent() {
        StringBuilder result = new StringBuilder();
        for (Message m: getSortedMessages()) {
            result.append(m.getText()).append('\n');
        }
        return result.toString();
    }

    public void jdoPostDetach(Object detached) {
        if (detached instanceof Discussion) {
            ((Discussion)detached).postDetach();
        }
    }

    private void postDetach() {
//        Map<String, ForumUser> user_id2object = new HashMap<String, ForumUser>();
//        for (Message message : messages) {
//            user_id2object.put(message.getUserID(), null);
//        }
//        UserService.getUsers(user_id2object);
//        for (Message message : messages) {
//            ForumUser user = user_id2object.get(message.getUserID());
//            if (user != null) {
//                message.setForumUser(user);
//            }
//        }
    }

    public void jdoPreDetach() {
        // TODO Auto-generated method stub

    }

    public void setSourceForgeID(Long id) {
        this.sourceForgeID = id;
    }

    public static Discussion find(Long id) {
        PersistenceManager pm = PMF.getFactory().getPersistenceManager();
        try {
            Discussion d = pm.getObjectById(Discussion.class, id);
            d = pm.detachCopy(d);
            return d;
        }
        catch (JDOObjectNotFoundException e) {
            return null;
        }
        finally {
            pm.close();
        }
    }

    public static Discussion findBySourceForgeID(PersistenceManager pm, Long id) {
        Query discussionsQuery = pm.newQuery(Discussion.class);
        discussionsQuery.setRange(0, 1);
        discussionsQuery.setFilter("sourceForgeID == paramID");
        discussionsQuery.declareParameters("Long paramID");
        List<Discussion> result = (List<Discussion>) discussionsQuery.execute(id);
        return result.isEmpty() ? null : pm.detachCopy(result.get(0));
    }

    public static Discussion findByRefID(String refID) {
        PersistenceManager pm = PMF.getFactory().getPersistenceManager();
        try {
            Query discussionsQuery = pm.newQuery(Discussion.class);
            discussionsQuery.setRange(0, 1);
            discussionsQuery.setFilter("creationOrder == param");
            discussionsQuery.declareParameters("String param");
            List<Discussion> result = (List<Discussion>) discussionsQuery.execute(refID);
            return result.isEmpty() ? null : pm.detachCopy(result.get(0));
        }
        finally {
            pm.close();
        }
    }

    public List<Message> getSortedMessages() {
        if (mySorted == null) {
            mySorted = Lists.newArrayList(this.messages);
        }
        sortMessages(mySorted);
        return Collections.unmodifiableList(mySorted);
    }
}
